/**
 * ResizeFilter.java
 * 
 * @date: 2012-02-15
 * @author: Xiaoyu Guo (MUN# 200982155)
 * This file is part of the course project for COMP 6752, 
 * Application of Computer Graphics. All the file is written
 * by myself.
 */
package mun.cs6752.project.filter.resize;

import java.awt.image.BufferedImage;

import mun.cs6752.project.filter.abstractClasses.AbstractImageFilter;
import mun.cs6752.project.filter.abstractClasses.AbstractImageFilterControl;
import mun.cs6752.project.filter.interfaces.IImageFilterParameter;
import mun.cs6752.project.filter.interfaces.IImageProvider;
import mun.cs6752.project.filter.resize.ResizeFilterParameter.ResizeMethod;

/**
 * @author Xiaoyu Guo
 *
 */
public class ResizeFilter extends AbstractImageFilter {

    /**
     * @param provider
     * @param name
     */
    public ResizeFilter(IImageProvider provider) {
        super(provider, "Resize");
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#execute(java.awt.image.BufferedImage, mun.cs6752.project.filter.interfaces.IImageFilterParameter)
     */
    @Override
    public BufferedImage execute(BufferedImage srcImage, IImageFilterParameter param) {
        ResizeFilterParameter p = null;
        if(param == null || !(param instanceof ResizeFilterParameter)){
            p = new ResizeFilterParameter(
                provider.getSourceImage().getWidth(), 
                provider.getSourceImage().getHeight(), 
                ResizeMethod.Bicubic
            );
        } else {
            p = (ResizeFilterParameter) param;
        }
        
        BufferedImage destImage = new BufferedImage(
            p.getNewWidth(), 
            p.getNewHeight(), 
            BufferedImage.TYPE_INT_RGB
        );
        
        // backward transformation
        float xRatio = (float)provider.getSourceImage().getWidth() / (float)p.getNewWidth();
        float yRatio = (float)provider.getSourceImage().getHeight() / (float)p.getNewHeight();
        for(int y = 0; y < p.getNewHeight(); y++){
            float srcY = (float)y * yRatio; 
            for(int x = 0; x < p.getNewWidth(); x++){
                float srcX = (float)x * xRatio;
                int newRGB = 0;
                switch(p.getMethod()){
                case NearestNeighbour:
                    newRGB = sampleNearestNeighbour(provider.getSourceImage(), srcX, srcY);
                    break;
                case Bilinear:
                    newRGB = sampleBilinear(provider.getSourceImage(), srcX, srcY);
                    break;
                case Bicubic:
                    newRGB = sampleBicubic(provider.getSourceImage(), srcX, srcY);
                    break;
                }
                
                destImage.setRGB(x, y, newRGB);
            }
        }
        
        return destImage;
    }

    /**
     * @param sourceImage
     * @param srcX
     * @param srcY
     * @return
     */
    private int sampleBicubic(BufferedImage sourceImage, float srcX, float srcY) {
        // TODO Auto-generated method stub
        return 0;
    }

    /**
     * @param sourceImage
     * @param srcX
     * @param srcY
     * @return
     */
    private int sampleBilinear(BufferedImage sourceImage, float srcX, float srcY) {
        int x0 = (int) Math.floor(srcX);
        int x1 = (int) Math.ceil(srcX);
        int y0 = (int) Math.floor(srcY);
        int y1 = (int) Math.ceil(srcY);
        
        if(x0 < 0){
            x0 = 0;
        }
        
        if(y0 < 0){
            y0 = 0;
        }
        
        if(x1 >= sourceImage.getWidth()){
            x1 = sourceImage.getWidth() - 1;
        }
        
        if(y1 >= sourceImage.getHeight()){
            y1 = sourceImage.getHeight() - 1;
        }
        
        int rgb00 = sourceImage.getRGB(x0, y0);
        int rgb01 = sourceImage.getRGB(x0, y1);
        int rgb10 = sourceImage.getRGB(x1, y0);
        int rgb11 = sourceImage.getRGB(x1, y1);
        
        int r = 0, g = 0, b = 0;
        
        if(x0 == x1 && y0 == y1){
            return rgb00;
        } else if(x0 == x1){
            r = (int) (getR(rgb00) * (y1 - srcY) + getR(rgb11) * (srcY - y0));
            g = (int) (getG(rgb00) * (y1 - srcY) + getG(rgb11) * (srcY - y0));
            b = (int) (getB(rgb00) * (y1 - srcY) + getB(rgb11) * (srcY - y0));
        } else if(y0 == y1){
            r = (int) (getR(rgb00) * (x1 - srcX) + getR(rgb11) * (srcX - x0));
            g = (int) (getG(rgb00) * (x1 - srcX) + getG(rgb11) * (srcX - x0));
            b = (int) (getB(rgb00) * (x1 - srcX) + getB(rgb11) * (srcX - x0));
        } else {
            r = (int) (getR(rgb00) * (x1 - srcX) * (y1 - srcY) + getR(rgb10) * (srcX - x0) * (y1 - srcY)
              + getR(rgb01) * (x1 - srcX) * (srcY - y0) + getR(rgb11) * (srcX - x0) * (srcY - y0));
            
            g = (int) (getG(rgb00) * (x1 - srcX) * (y1 - srcY) + getG(rgb10) * (srcX - x0) * (y1 - srcY)
                    + getG(rgb01) * (x1 - srcX) * (srcY - y0) + getG(rgb11) * (srcX - x0) * (srcY - y0));
            
            b = (int) (getB(rgb00) * (x1 - srcX) * (y1 - srcY) + getB(rgb10) * (srcX - x0) * (y1 - srcY)
                    + getB(rgb01) * (x1 - srcX) * (srcY - y0) + getB(rgb11) * (srcX - x0) * (srcY - y0));
            
        }
        int result = getRGB(r, g, b);
        
        return result;
    }

    /**
     * @param sourceImage
     * @param srcX
     * @param srcY
     * @return
     */
    private int sampleNearestNeighbour(BufferedImage sourceImage, float srcX, float srcY) {
        return sourceImage.getRGB((int)srcX, (int)srcY);
    }

    /* (non-Javadoc)
     * @see mun.cs6752.project.filter.interfaces.IImageFilter#getControl()
     */
    @Override
    public AbstractImageFilterControl getControl() {
        return new ResizeFilterControl(provider, this);
    }

}
